<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="硬盘IO总结, 博客">
    <meta name="description" content="个人学习储备知识与经验的秘密基地">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <!-- Global site tag (gtag.js) - Google Analytics -->

<script async src="https://www.googletagmanager.com/gtag/js?id=G-KDXT8Y4CLW"></script>
<script>
    window.dataLayer = window.dataLayer || [];
    function gtag() {
        dataLayer.push(arguments);
    }

    gtag('js', new Date());
    gtag('config', 'G-KDXT8Y4CLW');
</script>


    <title>硬盘IO总结 | 不二博客</title>
    <link rel="icon" type="image/png" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/favicon.png">

    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/css/matery.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/css/my.css">

    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/jquery/jquery.min.js"></script>

<meta name="generator" content="Hexo 6.3.0">
<style>.github-emoji { position: relative; display: inline-block; width: 1.2em; min-height: 1.2em; overflow: hidden; vertical-align: top; color: transparent; }  .github-emoji > span { position: relative; z-index: 10; }  .github-emoji img, .github-emoji .fancybox { margin: 0 !important; padding: 0 !important; border: none !important; outline: none !important; text-decoration: none !important; user-select: none !important; cursor: auto !important; }  .github-emoji img { height: 1.2em !important; width: 1.2em !important; position: absolute !important; left: 50% !important; top: 50% !important; transform: translate(-50%, -50%) !important; user-select: none !important; cursor: auto !important; } .github-emoji-fallback { color: inherit; } .github-emoji-fallback img { opacity: 0 !important; }</style>
<link rel="alternate" href="/atom.xml" title="不二博客" type="application/atom+xml">
</head>




<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/" class="waves-effect waves-light">
                    
                    <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">不二博客</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/tags" class="waves-effect waves-light">
      
      <i class="fas fa-tags" style="zoom: 0.6;"></i>
      
      <span>标签</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/categories" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/contact" class="waves-effect waves-light">
      
      <i class="fas fa-comments" style="zoom: 0.6;"></i>
      
      <span>留言板</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友情链接</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">不二博客</div>
        <div class="logo-desc">
            
            个人学习储备知识与经验的秘密基地
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/tags" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-tags"></i>
			
			标签
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/categories" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/contact" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-comments"></i>
			
			留言板
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友情链接
		</a>
          
        </li>
        
        
        <li><div class="divider"></div></li>
        <li>
            <a href="https://github.com/weiyouwozuiku" class="waves-effect waves-light" target="_blank">
                <i class="fab fa-github-square fa-fw"></i>Fork Me
            </a>
        </li>
        
    </ul>
</div>


        </div>

        
            <style>
    .nav-transparent .github-corner {
        display: none !important;
    }

    .github-corner {
        position: absolute;
        z-index: 10;
        top: 0;
        right: 0;
        border: 0;
        transform: scale(1.1);
    }

    .github-corner svg {
        color: #0f9d58;
        fill: #fff;
        height: 64px;
        width: 64px;
    }

    .github-corner:hover .octo-arm {
        animation: a 0.56s ease-in-out;
    }

    .github-corner .octo-arm {
        animation: none;
    }

    @keyframes a {
        0%,
        to {
            transform: rotate(0);
        }
        20%,
        60% {
            transform: rotate(-25deg);
        }
        40%,
        80% {
            transform: rotate(10deg);
        }
    }
</style>

<a href="https://github.com/weiyouwozuiku" class="github-corner tooltipped hide-on-med-and-down" target="_blank"
   data-tooltip="Fork Me" data-position="left" data-delay="50">
    <svg viewBox="0 0 250 250" aria-hidden="true">
        <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
        <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
              fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
        <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
              fill="currentColor" class="octo-body"></path>
    </svg>
</a>
        
    </nav>

</header>

    
<script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/cryptojs/crypto-js.min.js"></script>
<script>
    (function() {
        let pwd = '';
        if (pwd && pwd.length > 0) {
            if (pwd !== CryptoJS.SHA256(prompt('请输入访问本文章的密码')).toString(CryptoJS.enc.Hex)) {
                alert('密码错误，将返回主页！');
                location.href = '/';
            }
        }
    })();
</script>




<div class="bg-cover pd-header post-cover" style="background-image: url('https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/PageImg/KV存储/硬盘IO总结.jpeg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title">硬盘IO总结</h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/tags/%E7%A1%AC%E7%9B%98IO/">
                                <span class="chip bg-color">硬盘IO</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/categories/KV%E5%AD%98%E5%82%A8/" class="post-category">
                                KV存储
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2021-10-03
                </div>
                

                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-check fa-fw"></i>更新日期:&nbsp;&nbsp;
                    2022-02-04
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-file-word fa-fw"></i>文章字数:&nbsp;&nbsp;
                    10.7k
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-clock fa-fw"></i>阅读时长:&nbsp;&nbsp;
                    37 分
                </div>
                

                
                    <div id="busuanzi_container_page_pv" class="info-break-policy">
                        <i class="far fa-eye fa-fw"></i>阅读次数:&nbsp;&nbsp;
                        <span id="busuanzi_value_page_pv"></span>
                    </div>
				
            </div>
        </div>
        <hr class="clearfix">

        
        <!-- 是否加载使用自带的 prismjs. -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/prism/prism.css">
        

        
        <!-- 代码块折行 -->
        <style type="text/css">
            code[class*="language-"], pre[class*="language-"] { white-space: pre-wrap !important; }
        </style>
        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <h2 id="背景">背景<a class="anchor" href="#背景">¶</a></h2>
<p>随着计算机硬件在过去10年中遵循摩尔定律的发展，通用计算机的CPU主频早已超过了4GHz，内存已早已进入了DDR4的时代。但是传统机械磁盘的读写性能并没有明显提升，而SSD的价格又过高。因此，探究传统机械硬盘的物理结构与性能优化方式具有重大学术与工程意义。</p>
<h2 id="磁盘的物理结构">磁盘的物理结构<a class="anchor" href="#磁盘的物理结构">¶</a></h2>
<p>由于单一盘片容量有限，一般硬盘都有两张以上的盘片，每个盘片有两面，都可记录信息，所以一张盘片对应着两个磁头。盘片被分为许多扇形的区域，每个区域叫一个扇区，硬盘中每个扇区的大小固定为512字节。盘片表面上以盘片中心为圆心，不同半径的同心圆称为磁道，不同盘片相同半径的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆，在许多场合，磁道和柱面可以互换使用。磁盘盘片垂直视角如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/KV%E5%AD%98%E5%82%A8/%E7%A3%81%E7%9B%98IO%E6%80%BB%E7%BB%93/%E7%A3%81%E7%9B%98%E7%9B%98%E7%89%87%E5%9E%82%E7%9B%B4%E8%A7%86%E8%A7%92.png" alt="磁盘盘片垂直视角.png"></p>
<p>盘片数量都受到限制，一般都在5片以内。盘片的编号自下向上从0开始，如最下边的盘片有0面和1面，再上一个盘片就编号为2面和3面。</p>
<p><strong>扇区是硬盘的最小存储单元</strong>。早期的硬盘每磁道扇区数相同，此时由磁盘基本参数可以计算出硬盘的容量：存储容量=磁头数<em>磁道（柱面）数</em>每道扇区数*每扇区字节数。由于每磁道扇区数相同，外圈磁道半径大，里圈磁道半径小，外圈和里圈扇区面积自然会不一样。同时，为了更好的读取数据，即使外圈扇区面积再大也只能和内圈扇区一样存放相同的字节数（512字节）。这样一来，外圈的记录密度就要比内圈小，会浪费大量的存储空间。</p>
<p>在现代磁盘驱动器中，<strong>每个物理扇区都由两个基本部分组成，即扇区头区域（通常称为“ ID”）和数据区域</strong>。扇区头包含驱动器和控制器使用的信息。该信息包括同步字节，地址标识，缺陷标志以及错误检测和纠正信息。如果数据区域不可靠，则标头还可以包含要使用的备用地址。地址标识用于确保驱动器的机械手已将读/写头定位在正确的位置上。数据区域包含同步字节，用户数据和纠错码（ECC），用于检查并可能纠正可能已引入数据中的错误。</p>
<h3 id="磁盘扇区划分方式">磁盘扇区划分方式<a class="anchor" href="#磁盘扇区划分方式">¶</a></h3>
<h4 id="CLV技术">CLV技术<a class="anchor" href="#CLV技术">¶</a></h4>
<p>一开始的技术叫做CLV，称为恒定线速度，这个技术要求无论在哪个圈上，线速度都要一样，所以对马达的要求非常高，寿命非常短，在低于12倍速的光驱中使用的技术。光碟片和硬碟不同，光碟片上每个部分的密度都是一样的，在同样旋转一圈的情况下，圆周较长的外圈部分在读取资料时会比内圈部分快，所谓的恒定线速度是指从内到外都是同样的读取速度，而为了保持一开始速度，读到外圈时会降低光碟片的转速来配合读取速度，读到内圈时会提高转速。</p>
<h4 id="CAV技术">CAV技术<a class="anchor" href="#CAV技术">¶</a></h4>
<p>CLV因为不停的更改马达的转速，会对机器的寿命造成一定的影响，而且磁盘转速也不能无限制的加快。后来又有了一种磁盘技术叫做CAV，叫做恒定角速度，马达的转速恒定，寿命有了很大提高，光盘上的内沿数据比外沿数据传输速度要低，越往外越能体现光驱的速度，倍速指的是最高数据传输率。但是也有缺点，浪费会很大，因为磁头读盘片的扫描频率基本是恒定的，外圈的有效磁介质单元会很稀疏。这时候，各个磁道的扇区数应该是一样的。</p>
<h4 id="ZBR技术">ZBR技术<a class="anchor" href="#ZBR技术">¶</a></h4>
<p>在计算机存储中，区域位记录（ZBR）是磁盘驱动器用来优化磁道以增加数据容量的一种方法。它通过在外部磁道上每个区域放置比内部磁道更多的扇区来实现此目的。这与其他方法相反，例如恒定角速度（CAV）驱动器，其中每个磁道的扇区数相同。在由大致同心的轨道组成的磁盘上（无论是实现为单独的圆形轨道还是实现为单个螺旋轨道），物理轨道的长度（周长）随着距中心轮毂的距离增加而增加。</p>
<p>如下图片所示，可以看到粉红色、绿色、灰色部分的扇区数量不一样。</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/KV%E5%AD%98%E5%82%A8/%E7%A3%81%E7%9B%98IO%E6%80%BB%E7%BB%93/ZBR.png" alt="ZBR.png"></p>
<p>如今的硬盘都使用ZBR（Zoned Bit Recording，区位记录）技术，盘片表面由里向外划分为数个区域，不同区域的磁道扇区数目不同，同一区域内各磁道扇区数相同，盘片外圈区域磁道长扇区数目较多，内圈区域磁道短扇区数目较少，大体实现了等密度，从而获得了更多的存储空间。此时，由于每磁道扇区数各不相同，所以传统的容量计算公式就不再适用。实际上如今的硬盘大多使用LBA（Logical Block Addressing）逻辑块寻址模式，知道LBA后即可计算出硬盘容量。</p>
<p>设置多少个区域，每个区域的扇区数设定也都是有讲究的。否则会在向内跨区域读写时造成传输率下降过大而影响整体性能。大多数产品划分了16个区域，最外圈的每磁道扇区数正好是最内圈的一倍，与最大的持续传输率的参数基本成比例。比如0到100磁道采用每小时120码的速度，101到200磁道采用每小时100码的速度，201到300采用每小时80码的速度。</p>
<h3 id="物理扇区与逻辑扇区">物理扇区与逻辑扇区<a class="anchor" href="#物理扇区与逻辑扇区">¶</a></h3>
<p>关于物理扇区（physical setctor）与逻辑扇区，这个还得扯上扇区大小，由于近年来，随着对硬盘容量的要求不断增加，为了提高数据记录密度，硬盘厂商往往采用增大扇区大小的方法，于是出现了扇区大小为4096字节的硬盘。我们将这样的扇区称之为“物理扇区”。但是这样的大扇区会有兼容性问题，有的系统或软件无法适应。为了解决这个问题，硬盘内部将物理扇区在逻辑上划分为多个扇区片段并将其作为普通的扇区（一般为512字节大小）报告给操作系统及应用软件。这样的扇区片段我们称之为“逻辑扇区”。实际读写时由硬盘内的程序（固件）负责在逻辑扇区与物理扇区之间进行转换，上层程序“感觉”不到物理扇区的存在。</p>
<p><strong>逻辑扇区是硬盘可以接受读写指令的最小操作单元，是操作系统及应用程序可以访问的扇区</strong>，多数情况下其大小为512字节。我们通常所说的扇区一般就是指的逻辑扇区。<strong>物理扇区是硬盘底层硬件意义上的扇区，是实际执行读写操作的最小单元。是只能由硬盘直接访问的扇区，操作系统及应用程序一般无法直接访问物理扇区</strong>。一个物理扇区可以包含一个或多个逻辑扇区（比如多数硬盘的物理扇区包含了8个逻辑扇区）。当要读写某个逻辑扇区时，硬盘底层在实际操作时都会读写逻辑扇区所在的整个物理扇区。</p>
<p><strong>同一块硬盘上的扇区大小一定是一致的</strong>，不可能存在多种不同大小的扇区。</p>
<h2 id="Block-Cluster">Block/Cluster<a class="anchor" href="#Block-Cluster">¶</a></h2>
<p>块（Block）/簇（Cluster）是逻辑上的概念，或者说是虚拟出来的概念。 分别对应Linux与Windows操作系统中的概念。注意：有些文章或资料叫做磁盘块/磁盘簇。</p>
<p>Unix与Linux系统中，块（Block）是操作系统中最小的逻辑存储单位。操作系统与磁盘打交道的最小单位是块（Block）。在Windows下如NTFS等文件系统中叫做簇。</p>
<p>每个簇或者块可以包括2、4、8、16、32、64…2的n次方个扇区。现在电脑一般都是4096大小。</p>
<p>之所以不直接使用扇区的概念是因为，扇区size太小，数量众多时寻址困难。OS将相邻的扇区进行整合，再进行整体的操作。</p>
<h2 id="影响硬盘性能的因素">影响硬盘性能的因素<a class="anchor" href="#影响硬盘性能的因素">¶</a></h2>
<p>影响磁盘的关键因素是磁盘服务时间，即磁盘完成一个I/O请求所花的时间。</p>
<p>磁盘服务时间=寻道时间+旋转延迟+数据传输时间</p>
<h3 id="寻道时间">寻道时间<a class="anchor" href="#寻道时间">¶</a></h3>
<p><img src="https://math.now.sh?inline=T_%7Bseek%7D" style="filter: opacity(95%);transform:scale(0.85);text-align:center;display:inline-block;margin: 0;">是指将读写磁头移动至正确的磁道上所需要的时间。寻道时间越短，I/O操作越快，目前磁盘的平均寻道时间一般在3-15ms。</p>
<h3 id="旋转延迟">旋转延迟<a class="anchor" href="#旋转延迟">¶</a></h3>
<p><img src="https://math.now.sh?inline=T_%7Brotation%7D" style="filter: opacity(95%);transform:scale(0.85);text-align:center;display:inline-block;margin: 0;">是指盘片旋转将请求数据所在的扇区移动到读写磁盘下方所需要的时间。旋转延迟取决于磁盘转速，通常用磁盘旋转一周所需时间的1/2表示。比如：7200rpm的磁盘平均旋转延迟大约为60*1000/7200/2 = 4.17ms，而转速为15000rpm的磁盘其平均旋转延迟为2ms。</p>
<h3 id="数据传输时间">数据传输时间<a class="anchor" href="#数据传输时间">¶</a></h3>
<p><img src="https://math.now.sh?inline=T_%7Btransfer%7D" style="filter: opacity(95%);transform:scale(0.85);text-align:center;display:inline-block;margin: 0;">是指完成传输所请求的数据所需要的时间，它取决于数据传输率，其值等于数据大小除以数据传输率。目前IDE/ATA能达到133MB/s，SATA II可达到300MB/s的接口数据传输率，数据传输时间通常远小于前两部分消耗时间。简单计算时可忽略。</p>
<h2 id="衡量性能指标">衡量性能指标<a class="anchor" href="#衡量性能指标">¶</a></h2>
<p>机械硬盘的连续读写性能很好，但随机读写性能很差，这主要是因为磁头移动到正确的磁道上需要时间，随机读写时，磁头需要不停的移动，时间都浪费在了磁头寻址上，所以性能不高。衡量磁盘的重要主要指标是IOPS和吞吐量。</p>
<h3 id="IOPS">IOPS<a class="anchor" href="#IOPS">¶</a></h3>
<p>IOPS（Input/Output Per Second）即每秒的输入输出量（或读写次数），即指每秒内系统能处理的I/O请求数量。随机读写频繁的应用，如小文件存储等，关注随机读写性能，IOPS是关键衡量指标。可以推算出磁盘的IOPS = 1000ms / (Tseek + Trotation + Transfer)，如果忽略数据传输时间，理论上可以计算出随机读写最大的IOPS。常见磁盘的随机读写最大IOPS为：</p>
<ul>
<li>7200rpm的磁盘 IOPS = 76 IOPS</li>
<li>10000rpm的磁盘IOPS = 111 IOPS</li>
<li>15000rpm的磁盘IOPS = 166 IOPS</li>
</ul>
<h3 id="吞吐量">吞吐量<a class="anchor" href="#吞吐量">¶</a></h3>
<p>吞吐量（Throughput），指单位时间内可以成功传输的数据数量。顺序读写频繁的应用，如视频点播，关注连续读写性能、数据吞吐量是关键衡量指标。它主要取决于磁盘阵列的架构，通道的大小以及磁盘的个数。不同的磁盘阵列存在不同的架构，但他们都有自己的内部带宽，一般情况下，内部带宽都设计足够充足，不会存在瓶颈。磁盘阵列与服务器之间的数据通道对吞吐量影响很大，比如一个2Gbps的光纤通道，其所能支撑的最大流量仅为250MB/s。最后，当前面的瓶颈都不再存在时，硬盘越多的情况下吞吐量越大。</p>
<h2 id="OS层面的磁盘">OS层面的磁盘<a class="anchor" href="#OS层面的磁盘">¶</a></h2>
<p>虽然15000rpm的磁盘计算出的理论最大IOPS仅为166，但在实际运行环境中，实际磁盘的IOPS往往能够突破200甚至更高。这其实就是在系统调用过程中，操作系统进行了一系列的优化。</p>
<p>那么操作系统是如何操作硬盘的呢？类似于网络的分层结构，下图显示了Linux系统中对于磁盘的一次读请求在核心空间中所要经历的层次模型。从图中看出：对于磁盘的一次读请求，首先经过虚拟文件系统层（VFS Layer），其次是具体的文件系统层（例如Ext2），接下来是Cache层（Page Cache Layer）、通用块层（Generic Block Layer）、I/O调度层（I/O Scheduler Layer）、块设备驱动层（Block Device Driver Layer），最后是物理块设备层（Block Device Layer）。</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/KV%E5%AD%98%E5%82%A8/%E7%A3%81%E7%9B%98IO%E6%80%BB%E7%BB%93/OS%E8%A7%92%E5%BA%A6%E4%B8%8B%E7%9A%84%E7%A3%81%E7%9B%98.png" alt="OS角度下的磁盘.png"></p>
<h3 id="VFS">VFS<a class="anchor" href="#VFS">¶</a></h3>
<p>VFS（Virtual File System）虚拟文件系统是一种软件机制，更确切的说扮演着文件系统管理者的角色，与它相关的数据结构只存在于物理内存当中。它的作用是：屏蔽下层具体文件系统操作的差异，为上层的操作提供一个统一的接口。正是因为有了这个层次，Linux中允许众多不同的文件系统共存并且对文件的操作可以跨文件系统而执行。</p>
<p>VFS中包含着向物理文件系统转换的一系列数据结构，如VFS超级块、VFS的Inode、各种操作函数的转换入口等。Linux中VFS依靠四个主要的数据结构来描述其结构信息，分别为超级块、索引结点、目录项和文件对象。</p>
<p>超级块（Super Block）：超级块对象表示一个文件系统。它存储一个已安装的文件系统的控制信息，包括文件系统名称（比如Ext2）、文件系统的大小和状态、块设备的引用和元数据信息（比如空闲列表等等）。VFS超级块存在于内存中，它在文件系统安装时建立，并且在文件系统卸载时自动删除。同时需要注意的是对于每个具体的文件系统来说，也有各自的超级块，它们存放于磁盘。</p>
<p>索引结点（Inode）：索引结点对象存储了文件的相关元数据信息，例如：文件大小、设备标识符、用户标识符、用户组标识符等等。Inode分为两种：一种是VFS的Inode，一种是具体文件系统的Inode。前者在内存中，后者在磁盘中。所以每次其实是将磁盘中的Inode调进填充内存中的Inode，这样才是算使用了磁盘文件Inode。当创建一个文件的时候，就给文件分配了一个Inode。一个Inode只对应一个实际文件，一个文件也会只有一个Inode。</p>
<p>目录项（Dentry）：引入目录项对象的概念主要是出于方便查找文件的目的。不同于前面的两个对象，目录项对象没有对应的磁盘数据结构，只存在于内存中。一个路径的各个组成部分，不管是目录还是普通的文件，都是一个目录项对象。如，在路径/home/source/test.java中，目录 /, home, source和文件 test.java都对应一个目录项对象。VFS在查找的时候，根据一层一层的目录项找到对应的每个目录项的Inode，那么沿着目录项进行操作就可以找到最终的文件。</p>
<p>文件对象（File）：文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开，所以一个文件可以存在多个文件对象。一个文件对应的文件对象可能不是惟一的，但是其对应的索引节点和目录项对象肯定是惟一的。</p>
<h3 id="Ext2">Ext2<a class="anchor" href="#Ext2">¶</a></h3>
<p>VFS的下一层即是具体的文件系统，本节简要介绍下Linux的Ext2文件系统。</p>
<p>一个文件系统一般使用块设备上一个独立的逻辑分区。对于Ext2文件系统来说，硬盘分区首先被划分为一个个的Block，一个Ext2文件系统上的每个Block都是一样大小的。但是不同Ext2文件系统，Block大小可能不同，这是在创建Ext2系统决定的，一般为1k或者4k。由于Block数量很多，为了方便管理，Ext2将这些Block聚集在一起分为几个大的块组（Block Group），每个块组包含的等量的物理块，在块组的数据块中存储文件或目录。Ext2文件系统存储结构如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/KV%E5%AD%98%E5%82%A8/%E7%A3%81%E7%9B%98IO%E6%80%BB%E7%BB%93/ext2.png" alt="ext2.png"></p>
<p>Ext2中的Super Block和Inode Table分别对应VFS中的超级块和索引结点，存放在磁盘。每个块组都有一个块组描述符GDT（Group Descriptor Table），存储一个块组的描述信息，例如在这个块组中从哪里开始是Inode表，从哪里开始是数据块等等。Block Bitmap和Inode Bitmap分别表示Block和Inode是否空闲可用。Data Block数据块是用来真正存储文件内容数据的地方，下面我们看一下具体的存储规则。</p>
<p>在Ext2文件系统中所支持的Block大小有1K、2K、4K三种。在格式化时Block的大小就固定了，且每个Block都有编号，方便Inode的记录。每个Block内最多只能够放置一个文件的数据，如果文件大于Block的大小，则一个文件会占用多个Block；如果文件小于Block，则该Block的剩余容量就不能够再被使用了，即磁盘空间会浪费。下面看看Inode和Block的对应关系。</p>
<p>Inode要记录的数据非常多，但大小仅为固定的128字节，同时记录一个Block号码就需要4字节，假设一个文件有400MB且每个Block为4K时，那么至少也要十万笔Block号码的记录。Inode不可能有这么多的记录信息，因此Ext2将Inode记录Block号码的区域定义为12个直接、一个间接、一个双间接与一个三间接记录区。Inode存储结构如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/KV%E5%AD%98%E5%82%A8/%E7%A3%81%E7%9B%98IO%E6%80%BB%E7%BB%93/Inode%E7%BB%93%E6%9E%84.png" alt="Inode结构.png"></p>
<p>最左边为Inode本身（128 bytes），里面有12个直接指向Block号码的对照，这12笔记录能够直接取得Block号码。至于所谓的间接就是再拿一个Block来当作记录Block号码的记录区，如果文件太大时，就会使用间接的Block来记录编号。如上图，当中间接只是拿一个Block来记录额外的号码而已。 同理，如果文件持续长大，那么就会利用所谓的双间接，第一个Block仅再指出下一个记录编号的Block在哪里，实际记录的在第二个Block当中。依此类推，三间接就是利用第三层Block来记录编号。</p>
<h3 id="Page-Cache">Page Cache<a class="anchor" href="#Page-Cache">¶</a></h3>
<p>引入Cache层的目的是为了提高Linux操作系统对磁盘访问的性能。<strong>Cache层在内存中缓存了磁盘上的部分数据。当数据的请求到达时，如果在Cache中存在该数据且是最新的，则直接将数据传递给用户程序，免除了对底层磁盘的操作，提高了性能。Cache层也正是磁盘IOPS为什么能突破200的主要原因之一</strong>。</p>
<p>在Linux的实现中，文件Cache分为两个层面，<strong>一是Page Cache，另一个Buffer Cache</strong>，每一个Page Cache包含若干Buffer Cache。Page Cache主要用来作为文件系统上的文件数据的缓存来用，尤其是针对当进程对文件有read/write操作的时候。Buffer Cache则主要是设计用来在系统对块设备进行读写的时候，对块进行数据缓存的系统来使用。</p>
<p>磁盘Cache有两大功能：<strong>预读和回写</strong>。预读其实就是利用了局部性原理，具体过程是：对于每个文件的第一个读请求，系统读入所请求的页面并读入紧随其后的少数几个页面（通常是三个页面），这时的预读称为<strong>同步预读</strong>。对于第二次读请求，如果所读页面不在Cache中，即不在前次预读的页中，则表明文件访问不是顺序访问，系统继续采用同步预读；如果所读页面在Cache中，则表明前次预读命中，操作系统把预读页的大小扩大一倍，此时预读过程是异步的，应用程序可以不等预读完成即可返回，只要后台慢慢读页面即可，这时的预读称为<strong>异步预读</strong>。任何接下来的读请求都会处于两种情况之一：第一种情况是所请求的页面处于预读的页面中，这时继续进行异步预读；第二种情况是所请求的页面处于预读页面之外，这时系统就要进行同步预读。</p>
<p><strong>回写是通过暂时将数据存在Cache里，然后统一异步写到磁盘中</strong>。通过这种异步的数据I/O模式解决了程序中的计算速度和数据存储速度不匹配的鸿沟，减少了访问底层存储介质的次数，使存储系统的性能大大提高。Linux 2.6.32内核之前，采用pdflush机制来将脏页真正写到磁盘中，什么时候开始回写呢？下面两种情况下，脏页会被写回到磁盘：</p>
<ol>
<li>在空闲内存低于一个特定的阈值时，内核必须将脏页写回磁盘，以便释放内存。</li>
<li>当脏页在内存中驻留超过一定的阈值时，内核必须将超时的脏页写会磁盘，以确保脏页不会无限期地驻留在内存中。</li>
</ol>
<p>回写开始后，pdflush会持续写数据，直到满足以下两个条件：</p>
<ol>
<li>已经有指定的最小数目的页被写回到磁盘。</li>
<li>空闲内存页已经回升，超过了阈值。</li>
</ol>
<p>Linux 2.6.32内核之后，放弃了原有的pdflush机制，改成了bdi_writeback机制。bdi_writeback机制主要解决了原有fdflush机制存在的一个问题：在多磁盘的系统中，pdflush管理了所有磁盘的Cache，从而导致一定程度的I/O瓶颈。bdi_writeback机制为每个磁盘都创建了一个线程，专门负责这个磁盘的Page Cache的刷新工作，从而实现了每个磁盘的数据刷新在线程级的分离，提高了I/O性能。</p>
<p>回写机制存在的问题是回写不及时引发数据丢失（可由sync|fsync解决），回写期间读I/O性能很差。</p>
<h3 id="通用块层">通用块层<a class="anchor" href="#通用块层">¶</a></h3>
<p>通用块层的主要工作是：接收上层发出的磁盘请求，并最终发出I/O请求。该层隐藏了底层硬件块设备的特性，为块设备提供了一个通用的抽象视图。</p>
<p>对于VFS和具体的文件系统来说，块（Block）是基本的数据传输单元，当内核访问文件的数据时，它首先从磁盘上读取一个块。但是对于磁盘来说，扇区是最小的可寻址单元，块设备无法对比它还小的单元进行寻址和操作。由于扇区是磁盘的最小可寻址单元，所以块不能比扇区还小，只能整数倍于扇区大小，即一个块对应磁盘上的一个或多个扇区。一般来说，块大小是2的整数倍，而且由于Page Cache层的最小单元是页（Page），所以块大小不能超过一页的长度。</p>
<p>大多情况下，数据的传输通过DMA方式。旧的磁盘控制器，仅仅支持简单的DMA操作：每次数据传输，只能传输磁盘上相邻的扇区，即数据在内存中也是连续的。这是因为如果传输非连续的扇区，会导致磁盘花费更多的时间在寻址操作上。而现在的磁盘控制器支持“分散/聚合”DMA操作，这种模式下，数据传输可以在多个非连续的内存区域中进行。<strong>为了利用“分散/聚合”DMA操作，块设备驱动必须能处理被称为段（segments）的数据单元。一个段就是一个内存页面或一个页面的部分，它包含磁盘上相邻扇区的数据</strong>。</p>
<p>通用块层是粘合所有上层和底层的部分，一个页的磁盘数据布局如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/KV%E5%AD%98%E5%82%A8/%E7%A3%81%E7%9B%98IO%E6%80%BB%E7%BB%93/%E9%A1%B5%E6%95%B0%E6%8D%AE%E5%88%86%E5%B8%83.png" alt="页数据分布.png"></p>
<h3 id="I-O调度层">I/O调度层<a class="anchor" href="#I-O调度层">¶</a></h3>
<p>I/O调度层的功能是管理块设备的请求队列。即接收通用块层发出的I/O请求，缓存请求并试图合并相邻的请求。并根据设置好的调度算法，回调驱动层提供的请求处理函数，以处理具体的I/O请求。</p>
<p>如果简单地以内核产生请求的次序直接将请求发给块设备的话，那么块设备性能肯定让人难以接受，因为磁盘寻址是整个计算机中最慢的操作之一。为了优化寻址操作，内核不会一旦接收到I/O请求后，就按照请求的次序发起块I/O请求。为此Linux实现了几种I/O调度算法，算法基本思想就是通过合并和排序I/O请求队列中的请求，以此大大降低所需的磁盘寻道时间，从而提高整体I/O性能。</p>
<p>常见的I/O调度算法包括Noop调度算法（No Operation）、CFQ（完全公正排队I/O调度算法）、DeadLine（截止时间调度算法）、AS预测调度算法等。</p>
<ul>
<li>Noop算法：最简单的I/O调度算法。<strong>该算法仅适当合并用户请求，并不排序请求。新的请求通常被插在调度队列的开头或末尾，下一个要处理的请求总是队列中的第一个请求。这种算法是为不需要寻道的块设备设计的，如SSD</strong>。因为其他三个算法的优化是基于缩短寻道时间的，而SSD硬盘没有所谓的寻道时间且I/O响应时间非常短。</li>
<li>CFQ算法：算法的主要目标是在触发I/O请求的所有进程中确保磁盘I/O带宽的公平分配。算法使用许多个排序队列，存放了不同进程发出的请求。通过散列将同一个进程发出的请求插入同一个队列中。采用轮询方式扫描队列，从第一个非空队列开始，依次调度不同队列中特定个数（公平）的请求，然后将这些请求移动到调度队列的末尾。</li>
<li>Deadline算法：算法引入了两个排队队列分别包含读请求和写请求，两个最后期限队列包含相同的读和写请求。本质就是一个超时定时器，当请求被传给电梯算法时开始计时。一旦最后期限队列中的超时时间已到，就请求移至调度队列末尾。Deadline算法避免了电梯调度策略（为了减少寻道时间，会优先处理与上一个请求相近的请求）带来的对某个请求忽略很长一段时间的可能。</li>
<li>AS算法：AS算法本质上依据局部性原理，预测进程发出的读请求与刚被调度的请求在磁盘上可能是“近邻”。算法统计每个进程I/O操作信息，当刚刚调度了由某个进程的一个读请求之后，算法马上检查排序队列中的下一个请求是否来自同一个进程。如果是，立即调度下一个请求。否则，查看关于该进程的统计信息，如果确定进程可能很快发出另一个读请求，那么就延迟一小段时间。</li>
</ul>
<p>前文中计算出的IOPS是理论上的随机读写的最大IOPS，在随机读写中，每次I/O操作的寻址和旋转延时都不能忽略不计，有了这两个时间的存在也就限制了IOPS的大小。现在如果我们考虑在读取一个很大的存储连续分布在磁盘的文件，因为文件的存储的分布是连续的，磁头在完成一个读I/O操作之后，不需要重新寻址，也不需要旋转延时，在这种情况下我们能到一个很大的IOPS值。这时由于不再考虑寻址和旋转延时，则性能瓶颈仅是数据传输时延，假设数据传输时延为0.4ms，那么IOPS=1000 / 0.4 = 2500 IOPS。</p>
<p>在许多的开源框架如Kafka、HBase中，都通过追加写的方式来尽可能的将随机I/O转换为顺序I/O，以此来降低寻址时间和旋转延时，从而最大限度的提高IOPS。</p>
<h3 id="块设备驱动层">块设备驱动层<a class="anchor" href="#块设备驱动层">¶</a></h3>
<p>驱动层中的驱动程序对应具体的物理块设备。它从上层中取出I/O请求，并根据该I/O请求中指定的信息，通过向具体块设备的设备控制器发送命令的方式，来操纵设备传输数据。</p>
<h2 id="基于磁盘特性的设计">基于磁盘特性的设计<a class="anchor" href="#基于磁盘特性的设计">¶</a></h2>
<p>在了解Linux系统中请求到达磁盘的一次完整过程，期间Linux通过Cache以及排序合并I/O请求来提高系统的性能。其本质就是由于磁盘随机读写慢、顺序读写快。本节针对常见开源系统阐述一些基于磁盘I/O特性的设计技巧。</p>
<h3 id="采用追加写">采用追加写<a class="anchor" href="#采用追加写">¶</a></h3>
<p>在进行系统设计时，良好的读性能和写性能往往不可兼得。<strong>在许多常见的开源系统中都是优先在保证写性能的前提下来优化读性能</strong>。那么如何设计能让一个系统拥有良好的写性能呢？一个好的办法就是采用追加写，每次将数据添加到文件。由于完全是顺序的，所以可以具有非常好的写操作性能。但是这种方式也存在一些缺点：从文件中读一些数据时将会需要更多的时间：需要倒序扫描，直到找到所需要的内容。当然在一些简单的场景下也能够保证读操作的性能：</p>
<h4 id="数据是被整体访问的">数据是被整体访问的<a class="anchor" href="#数据是被整体访问的">¶</a></h4>
<p>HDFS建立在一次写多次读的模型之上。在HDFS中就是采用了追加写并且设计为高数据吞吐量；高吞吐量必然以高延迟为代价，所以HDFS并不适用于对数据访问要求低延迟的场景；由于采用是的追加写，也并不适用于任意修改文件的场景。HDFS设计为流式访问大文件，使用大数据块并且采用流式数据访问来保证数据被整体访问，同时最小化硬盘的寻址开销，只需要一次寻址即可，这时寻址时间相比于传输时延可忽略，从而也拥有良好的读性能。HDFS不适合存储小文件，原因之一是由于NameNode内存不足问题，还有就是因为访问大量小文件需要执行大量的寻址操作，并且需要不断的从一个datanode跳到另一个datanode，这样会大大降低数据访问性能。</p>
<h4 id="知道文件明确的偏移量">知道文件明确的偏移量<a class="anchor" href="#知道文件明确的偏移量">¶</a></h4>
<p>在Kafka中，采用消息追加的方式来写入每个消息，每个消息读写时都会利用Page Cache的预读和回写特性，同时partition中都使用顺序读写，以此来提高I/O性能。虽然Kafka能够根据偏移量查找到具体的某个消息，但是查找过程是顺序查找，因此如果数据很大的话，查找效率就很低。所以Kafka中采用了分段和索引的方式来解决查找效率问题。Kafka把一个patition大文件又分成了多个小文件段，每个小文件段以偏移量命名，通过多个小文件段，不仅可以使用二分搜索法很快定位消息，同时也容易定期清除或删除已经消费完的文件，减少磁盘占用。为了进一步提高查找效率，Kafka为每个分段后的数据建立了索引文件，并通过索引文件稀疏存储来降低元数据占用大小。一个段中数据对应结构如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/KV%E5%AD%98%E5%82%A8/%E7%A3%81%E7%9B%98IO%E6%80%BB%E7%BB%93/kafka%E6%AE%B5%E7%BB%93%E6%9E%84.png" alt="kafka段结构.png"></p>
<p>在面对更复杂的读场景（比如按key）时，如何来保证读操作的性能呢？简单的方式是像Kafka那样，将文件数据有序保存，使用二分查找来优化效率；或者通过建索引的方式来进行优化；也可以采用hash的方式将数据分割为不同的桶。以上的方法都能增加读操作的性能，但是由于在数据上强加了数据结构，又会降低写操作的性能。比如如果采用索引的方式来优化读操作，那么在更新索引时就需要更新B-tree中的特定部分，这时候的写操作就是随机写。那么有没有一种办法在保证写性能不损失的同时也提供较好的读性能呢？一个好的选择就是使用LSM-tree。LSM-tree与B-tree相比，LSM-tree牺牲了部分读操作，以此大幅提高写性能。</p>
<p>日志结构的合并树LSM（The Log-Structured Merge-Tree）是HBase，LevelDB等NoSQL数据库的存储引擎。Log-Structured的思想是将整个磁盘看做一个日志，在日志中存放永久性数据及其索引，每次都添加到日志的末尾。并且通过将很多小文件的存取转换为连续的大批量传输，使得对于文件系统的大多数存取都是顺序的，从而提高磁盘I/O。<strong>LSM-tree就是这样一种采用追加写、数据有序以及将随机I/O转换为顺序I/O的延迟更新，批量写入硬盘的数据结构</strong>。LSM-tree将数据的修改增量先保存在内存中，达到指定的大小限制后再将这些修改操作批量写入磁盘。因此比较旧的文件不会被更新，重复的纪录只会通过创建新的纪录来覆盖，这也就产生了一些冗余的数据。所以系统会周期性的合并一些数据，移除重复的更新或者删除纪录，同时也会删除上述的冗余。在进行读操作时，如果内存中没有找到相应的key，那么就是倒序从一个个磁盘文件中查找。如果文件越来越多那么读性能就会越来越低，目前的解决方案是采用页缓存来减少查询次数，周期合并文件也有助于提高读性能。在文件越来越多时，可通过布隆过滤器来避免大量的读文件操作。LSM-tree牺牲了部分读性能，以此来换取写入的最大化性能，特别适用于<strong>读需求低，会产生大量插入操作的应用环境</strong>。</p>
<h3 id="文件合并与元数据优化">文件合并与元数据优化<a class="anchor" href="#文件合并与元数据优化">¶</a></h3>
<p>目前的大多数文件系统，如XFS/Ext4、GFS、HDFS，在元数据管理、缓存管理等实现策略上都侧重大文件。上述基于磁盘I/O特性设计的系统都有一个共性特点就是都运行在这些文件系统之上。这些文件系统在面临海量小文件时在性能和存储效率方面都大幅降低，本节来探讨下海量小文件下的系统设计。</p>
<p><strong>常见文件系统在海量小文件应用下性能表现不佳的根本原因是磁盘最适合顺序的大文件I/O读写模式，而非常不适合随机的小文件I/O读写模式</strong>。主要原因体现在元数据管理低效和数据布局低效：</p>
<ul>
<li>
<p>元数据管理低效：由于小文件数据内容较少，因此元数据的访问性能对小文件访问性能影响巨大。Ext2文件系统中Inode和Data Block分别保存在不同的物理位置上，一次读操作需要至少经过两次的独立访问。在海量小文件应用下，Inode的频繁访问，使得原本的并发访问转变为了海量的随机访问，大大降低了性能。另外，大量的小文件会快速耗尽Inode资源，导致磁盘尽管有大量Data Block剩余也无法存储文件，会浪费磁盘空间。</p>
</li>
<li>
<p>数据布局低效：Ext2在Inode中使用多级指针来索引数据块。对于大文件，数据块的分配会尽量连续，这样会具有比较好的空间局部性。但是对于小文件，数据块可能零散分布在磁盘上的不同位置，并且会造成大量的磁盘碎片，不仅造成访问性能下降，还大量浪费了磁盘空间。数据块一般为1KB、2KB或4KB，对于小于4KB的小文件，Inode与数据的分开存储破坏了空间局部性，同时也造成了大量的随机I/O。</p>
</li>
</ul>
<p>对于海量小文件应用，常见的I/O流程复杂也是造成磁盘性能不佳的原因。对于小文件，磁盘的读写所占用的时间较少，而用于文件的open()操作占用了绝大部分系统时间，导致磁盘有效服务时间非常低，磁盘性能低下。针对于问题的根源，优化的思路大体上分为：</p>
<ol>
<li>针对元数据管理低效，优化元数据的存储和管理。针对这两种优化方式，业内也出现了许多优秀的开源软件。</li>
<li>针对数据布局低效，采用小文件合并策略，将小文件合并为大文件。</li>
</ol>
<h4 id="元数据管理优化">元数据管理优化<a class="anchor" href="#元数据管理优化">¶</a></h4>
<p>一般来说元数据信息包括名称、文件大小、设备标识符、用户标识符、用户组标识符等等，<strong>在小文件系统中可以对元数据信息进行精简，仅保存足够的信息即可</strong>。元数据精简可以减少元数据通信延时，同时相同容量的Cache能存储更多的元数据，从而提高元数据使用效率。另外可以<strong>在文件名中就包含元数据信息，从而减少一个元数据的查询操作</strong>。最后<strong>针对特别小的一些文件，可以采取元数据和数据并存的策略，将数据直接存储在元数据之中，通过减少一次寻址操作从而大大提高性能</strong>。</p>
<ul>
<li>TFS中文件命名就隐含了位置信息等部分元数据，从而减少了一个元数据的查询操作。</li>
<li>在Rerserfs中，对于小于1KB的小文件，Rerserfs可以将数据直接存储在Inode中。</li>
</ul>
<h4 id="小文件合并">小文件合并<a class="anchor" href="#小文件合并">¶</a></h4>
<p>小文件合并为大文件后，首先减少了大量元数据，提高了元数据的检索和查询效率，降低了文件读写的I/O操作延时。其次将可能连续访问的小文件一同合并存储，增加了文件之间的局部性，将原本小文件间的随机访问变为了顺序访问，大大提高了性能。同时，合并存储能够有效的减少小文件存储时所产生的磁盘碎片问题，提高了磁盘的利用率。最后，合并之后小文件的访问流程也有了很大的变化，由原来许多的open操作转变为了seek操作，定位到大文件具体的位置即可。如何寻址这个大文件中的小文件呢？其实就是利用一个旁路数据库来记录每个小文件在这个大文件中的偏移量和长度等信息。其实小文件合并的策略本质上就是通过分层的思想来存储元数据。中控节点存储一级元数据，也就是大文件与底层块的对应关系；数据节点存放二级元数据，也就是最终的用户文件在这些一级大块中的存储位置对应关系，经过两级寻址来读写数据。</p>
<p>淘宝的TFS就采用了小文件合并存储的策略。TFS中默认Block大小为64M，每个块中会存储许多不同的小文件，但是这个块只占用一个Inode。假设一个Block为64M，数量级为1PB。那么NameServer上会有 1 * 1024 * 1024 * 1024 / 64 = 16.7M个Block。假设每个Block的元数据大小为0.1K，则占用内存不到2G。在TFS中，文件名中包含了Block ID和File ID，通过Block ID定位到具体的DataServer上，然后DataServer会根据本地记录的信息来得到File ID所在Block的偏移量，从而读取到正确的文件内容。TFS一次读过程如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/KV%E5%AD%98%E5%82%A8/%E7%A3%81%E7%9B%98IO%E6%80%BB%E7%BB%93/TFS.png" alt="TFS.png"></p>
<h2 id="Tips">Tips<a class="anchor" href="#Tips">¶</a></h2>
<h3 id="电梯调度算法">电梯调度算法<a class="anchor" href="#电梯调度算法">¶</a></h3>
<p>如果电梯到了你这个楼层，方向正好和你的需求一致，那么电梯就会停止，否则电梯会继续运行。</p>
<h2 id="参考文献">参考文献<a class="anchor" href="#参考文献">¶</a></h2>
<ol>
<li>IBM developerWorks，<a target="_blank" rel="noopener" href="https://www.ibm.com/developerworks/cn/aix/library/1203_weixy_aixio/">AIX 下磁盘 I/O 性能分析</a>，2012。</li>
<li>CSDN博客频道，<a target="_blank" rel="noopener" href="https://blog.csdn.net/hanchengxi/article/details/19089589">磁盘性能评价指标—IOPS和吞吐量</a>，2014。</li>
<li>IBM developerWorks，<a target="_blank" rel="noopener" href="https://www.ibm.com/developerworks/cn/linux/l-cn-read/">read 系统调用剖析</a>，2008。</li>
<li>IBM developerWorks，<a target="_blank" rel="noopener" href="https://developer.ibm.com/alert-zh/">从文件 I/O 看 Linux 的虚拟文件系统</a>，2007。</li>
<li>CSDN博客频道，<a target="_blank" rel="noopener" href="http://blog.csdn.net/kai_ding/article/details/17322787">Linux文件系统预读</a>，2013。</li>
<li>Linux Kernel Exploration，<a target="_blank" rel="noopener" href="http://www.ilinuxkernel.com/files/Linux.Generic.Block.Layer.pdf">Linux通用块设备层</a>。</li>
<li>CSDN博客频道，<a target="_blank" rel="noopener" href="http://blog.csdn.net/hustyangju/article/details/40507647">Linux块设备的IO调度算法和回写机制</a>，2014。</li>
<li>Apache，<a target="_blank" rel="noopener" href="http://kafka.apache.org/">Kafka</a>。</li>
<li>Taobao，<a target="_blank" rel="noopener" href="http://tfs.taobao.org/">Taobao File System</a>。</li>
<li>美团技术团队，<a target="_blank" rel="noopener" href="https://tech.meituan.com/2017/05/19/about-desk-io.html">磁盘I/O那些事</a>，2017。</li>
<li>CSDN博客频道，<a target="_blank" rel="noopener" href="https://www.cnblogs.com/kerrycode/p/12701772.html">存储基础知识：扇区与块/簇</a>，2020。</li>
</ol>

                
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/about" rel="external nofollow noreferrer">不二</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="http://buerlog.top/2021/10/03/kv-cun-chu/ying-pan-io-zong-jie/">http://buerlog.top/2021/10/03/kv-cun-chu/ying-pan-io-zong-jie/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/about" target="_blank">不二</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/tags/%E7%A1%AC%E7%9B%98IO/">
                                    <span class="chip bg-color">硬盘IO</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <style>
    #reward {
        margin: 40px 0;
        text-align: center;
    }

    #reward .reward-link {
        font-size: 1.4rem;
        line-height: 38px;
    }

    #reward .btn-floating:hover {
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 5px 15px rgba(0, 0, 0, 0.2);
    }

    #rewardModal {
        width: 320px;
        height: 350px;
    }

    #rewardModal .reward-title {
        margin: 15px auto;
        padding-bottom: 5px;
    }

    #rewardModal .modal-content {
        padding: 10px;
    }

    #rewardModal .close {
        position: absolute;
        right: 15px;
        top: 15px;
        color: rgba(0, 0, 0, 0.5);
        font-size: 1.3rem;
        line-height: 20px;
        cursor: pointer;
    }

    #rewardModal .close:hover {
        color: #ef5350;
        transform: scale(1.3);
        -moz-transform:scale(1.3);
        -webkit-transform:scale(1.3);
        -o-transform:scale(1.3);
    }

    #rewardModal .reward-tabs {
        margin: 0 auto;
        width: 210px;
    }

    .reward-tabs .tabs {
        height: 38px;
        margin: 10px auto;
        padding-left: 0;
    }

    .reward-content ul {
        padding-left: 0 !important;
    }

    .reward-tabs .tabs .tab {
        height: 38px;
        line-height: 38px;
    }

    .reward-tabs .tab a {
        color: #fff;
        background-color: #ccc;
    }

    .reward-tabs .tab a:hover {
        background-color: #ccc;
        color: #fff;
    }

    .reward-tabs .wechat-tab .active {
        color: #fff !important;
        background-color: #22AB38 !important;
    }

    .reward-tabs .alipay-tab .active {
        color: #fff !important;
        background-color: #019FE8 !important;
    }

    .reward-tabs .reward-img {
        width: 210px;
        height: 210px;
    }
</style>

<div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/medias/reward/alipay.jpg" class="reward-img" alt="支付宝打赏二维码">
                    </div>
                    <div id="wechat">
                        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/medias/reward/wechat.png" class="reward-img" alt="微信打赏二维码">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/2021/10/04/cheng-xu-she-ji/bu-long-guo-lu-qi-xue-xi/">
                    <div class="card-image">
                        
                        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/PageImg/程序设计/布隆过滤器.jpg" class="responsive-img" alt="布隆过滤器学习">
                        
                        <span class="card-title">布隆过滤器学习</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2021-10-04
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/" class="post-category">
                                    经验总结
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8/">
                        <span class="chip bg-color">布隆过滤器</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/2021/09/30/fen-bu-shi/fen-bu-shi-yi-zhi-xing-suan-fa-xue-xi-zong-jie/">
                    <div class="card-image">
                        
                        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/PageImg/分布式/分布式一致性算法学习总结.jpg" class="responsive-img" alt="分布式一致性算法学习总结">
                        
                        <span class="card-title">分布式一致性算法学习总结</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2021-09-30
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F/" class="post-category">
                                    分布式
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/Raft/">
                        <span class="chip bg-color">Raft</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>


<script>
    $('#articleContent').on('copy', function (e) {
        // IE8 or earlier browser is 'undefined'
        if (typeof window.getSelection === 'undefined') return;

        var selection = window.getSelection();
        // if the selection is short let's not annoy our users.
        if (('' + selection).length < Number.parseInt('120')) {
            return;
        }

        // create a div outside of the visible area and fill it with the selected text.
        var bodyElement = document.getElementsByTagName('body')[0];
        var newdiv = document.createElement('div');
        newdiv.style.position = 'absolute';
        newdiv.style.left = '-99999px';
        bodyElement.appendChild(newdiv);
        newdiv.appendChild(selection.getRangeAt(0).cloneContents());

        // we need a <pre> tag workaround.
        // otherwise the text inside "pre" loses all the line breaks!
        if (selection.getRangeAt(0).commonAncestorContainer.nodeName === 'PRE') {
            newdiv.innerHTML = "<pre>" + newdiv.innerHTML + "</pre>";
        }

        var url = document.location.href;
        newdiv.innerHTML += '<br />'
            + '来源: 不二博客<br />'
            + '文章作者: 不二<br />'
            + '文章链接: <a href="' + url + '">' + url + '</a><br />'
            + '本文章著作权归作者所有，任何形式的转载都请注明出处。';

        selection.selectAllChildren(newdiv);
        window.setTimeout(function () {bodyElement.removeChild(newdiv);}, 200);
    });
</script>


<!-- 代码块功能依赖 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card" style="background-color: white;">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h2, h3, h4, h5, h6'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h2, h3, h4, h5, h6').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/aplayer/APlayer.min.css">
<style>
    .aplayer .aplayer-lrc p {
        
        display: none;
        
        font-size: 12px;
        font-weight: 700;
        line-height: 16px !important;
    }

    .aplayer .aplayer-lrc p.aplayer-lrc-current {
        
        display: none;
        
        font-size: 15px;
        color: #42b983;
    }

    
    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body {
        left: -66px !important;
    }

    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover {
        left: 0px !important;
    }

    
</style>
<div class="">
    
    <div class="row">
        <meting-js class="col l8 offset-l2 m10 offset-m1 s12"
                   server="netease"
                   type="song"
                   id="1901371647"
                   fixed='true'
                   autoplay='false'
                   theme='#42b983'
                   loop='all'
                   order='random'
                   preload='auto'
                   volume='0.7'
                   list-folded='true'
        >
        </meting-js>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/aplayer/APlayer.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>

    
    <div class="container row center-align" style="margin-bottom: 15px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
                <span id="year">2018-2023</span>
            
            <span id="year">2018</span>
            <a href="/about" target="_blank">不二</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
            &nbsp;<i class="fas fa-chart-area"></i>&nbsp;站点总字数:&nbsp;<span
                class="white-color">263.4k</span>&nbsp;字
            
            
            
            
            
            
            <span id="busuanzi_container_site_pv">
                |&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;<span id="busuanzi_value_site_pv"
                    class="white-color"></span>&nbsp;次
            </span>
            
            
            <span id="busuanzi_container_site_uv">
                |&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;<span id="busuanzi_value_site_uv"
                    class="white-color"></span>&nbsp;人
            </span>
            
            <br>
            
            <span id="sitetime">载入运行时间...</span>
            <script>
                function siteTime() {
                    var seconds = 1000;
                    var minutes = seconds * 60;
                    var hours = minutes * 60;
                    var days = hours * 24;
                    var years = days * 365;
                    var today = new Date();
                    var startYear = "2018";
                    var startMonth = "5";
                    var startDate = "25";
                    var startHour = "17";
                    var startMinute = "20";
                    var startSecond = "53";
                    var todayYear = today.getFullYear();
                    var todayMonth = today.getMonth() + 1;
                    var todayDate = today.getDate();
                    var todayHour = today.getHours();
                    var todayMinute = today.getMinutes();
                    var todaySecond = today.getSeconds();
                    var t1 = Date.UTC(startYear, startMonth, startDate, startHour, startMinute, startSecond);
                    var t2 = Date.UTC(todayYear, todayMonth, todayDate, todayHour, todayMinute, todaySecond);
                    var diff = t2 - t1;
                    var diffYears = Math.floor(diff / years);
                    var diffDays = Math.floor((diff / days) - diffYears * 365);
                    var diffHours = Math.floor((diff - (diffYears * 365 + diffDays) * days) / hours);
                    var diffMinutes = Math.floor((diff - (diffYears * 365 + diffDays) * days - diffHours * hours) /
                        minutes);
                    var diffSeconds = Math.floor((diff - (diffYears * 365 + diffDays) * days - diffHours * hours -
                        diffMinutes * minutes) / seconds);
                    if (startYear == todayYear) {
                        document.getElementById("year").innerHTML = todayYear;
                        document.getElementById("sitetime").innerHTML = "本站已安全运行 " + diffDays + " 天 " + diffHours +
                            " 小时 " + diffMinutes + " 分钟 " + diffSeconds + " 秒";
                    } else {
                        document.getElementById("year").innerHTML = startYear + " - " + todayYear;
                        document.getElementById("sitetime").innerHTML = "本站已安全运行 " + diffYears + " 年 " + diffDays +
                            " 天 " + diffHours + " 小时 " + diffMinutes + " 分钟 " + diffSeconds + " 秒";
                    }
                }
                setInterval(siteTime, 1000);
            </script>
            
            <br>
            
        </div>
        <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://github.com/weiyouwozuiku" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="mailto:weiyouwozuiku@gmail.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
        <i class="fas fa-envelope-open"></i>
    </a>







    <a href="tencent://AddContact/?fromId=50&fromSubId=1&subcmd=all&uin=1104891151" class="tooltipped" target="_blank" data-tooltip="QQ联系我: 1104891151" data-position="top" data-delay="50">
        <i class="fab fa-qq"></i>
    </a>





    <a href="https://www.zhihu.com/people/he-he-9-35-47" class="tooltipped" target="_blank" data-tooltip="关注我的知乎: https://www.zhihu.com/people/he-he-9-35-47" data-position="top" data-delay="50">
        <i class="fab fa-zhihu1">知</i>
    </a>



    <a href="/atom.xml" class="tooltipped" target="_blank" data-tooltip="RSS 订阅" data-position="top" data-delay="50">
        <i class="fas fa-rss"></i>
    </a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>

    <script src='https://unpkg.com/mermaid@8.14.0/dist/mermaid.min.js'></script>
    <script>
      if (window.mermaid) {
        mermaid.initialize({theme: 'forest'});
      }
    </script>
  

    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/materialize/materialize.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/aos/aos.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/js/matery.js"></script>

    <!-- Baidu Analytics -->

<script>
    var _hmt = _hmt || [];
    (function () {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?943a68f473d421f2d98228f74edc63d0";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
    })();
</script>

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    
    <script async src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    <!--腾讯兔小巢-->
    
    
    <script type="text/javascript" color="0,0,255"
        pointColor="0,0,255" opacity='0.7'
        zIndex="-1" count="150"
        src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/background/canvas-nest.js"></script>
    

    
    
    <script type="text/javascript" size="150" alpha='0.6'
        zIndex="-1" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/background/ribbon-refresh.min.js" async="async"></script>
    

    
    <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/background/ribbon-dynamic.js" async="async"></script>
    

    
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
